//
// DotNetNuke - http://www.dotnetnuke.com
// Copyright (c) 2002-2010
// by Perpetual Motion Interactive Systems Inc. ( http://www.perpetualmotion.ca )
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated 
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation 
// the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and 
// to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions 
// of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 
// TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 
// CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
// DEALINGS IN THE SOFTWARE.
//

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text;
using DotNetNuke.Common.Utilities;
using DotNetNuke.Entities.Modules;
using DotNetNuke.Entities.Portals;
namespace DotNetNuke.Services.ModuleCache
{
	public class FileProvider : ModuleCachingProvider
	{
		private const string DataFileExtension = ".data.resources";
		private const string AttribFileExtension = ".attrib.resources";
		public FileProvider() : base()
		{
		}
		private string GenerateCacheKeyHash(int tabModuleId, string cacheKey)
		{
			byte[] hash = ASCIIEncoding.ASCII.GetBytes(cacheKey);
			System.Security.Cryptography.MD5CryptoServiceProvider md5 = new System.Security.Cryptography.MD5CryptoServiceProvider();
			hash = md5.ComputeHash(hash);
			return tabModuleId.ToString() + "_" + ByteArrayToString(hash);
		}
		private static string GetAttribFileName(int tabModuleId, string cacheKey)
		{
			return string.Concat(GetCacheFolder(), cacheKey, AttribFileExtension);
		}
		private static int GetCachedItemCount(int tabModuleId)
		{
			return Directory.GetFiles(GetCacheFolder(), String.Format("*{0}", FileProvider.DataFileExtension)).Length;
		}
		private static string GetCachedOutputFileName(int tabModuleId, string cacheKey)
		{
			return string.Concat(GetCacheFolder(), cacheKey, DataFileExtension);
		}
		private static string GetCacheFolder(int portalId)
		{
			PortalController portalController = new PortalController();
			PortalInfo portalInfo = portalController.GetPortal(portalId);

            string homeDirectoryMapPath = portalInfo.HomeDirectoryMapPath;
            string cacheFolder = Null.NullString;

            if ((!string.IsNullOrEmpty(homeDirectoryMapPath)))
            {
                cacheFolder = string.Concat(homeDirectoryMapPath, "Cache\\Pages\\");
                if (!Directory.Exists(cacheFolder))
                {
                    Directory.CreateDirectory(cacheFolder);
                }
            }


			return cacheFolder;
		}
		private static string GetCacheFolder()
		{
			int portalId = PortalController.GetCurrentPortalSettings().PortalId;
			return GetCacheFolder(portalId);
		}
		private bool IsFileExpired(string file)
		{
			System.IO.StreamReader oRead = null;
			try {
				oRead = System.IO.File.OpenText(file);
				System.DateTime expires = System.DateTime.Parse(oRead.ReadLine(), CultureInfo.InvariantCulture);
				if (expires < System.DateTime.UtcNow) {
					return true;
				} else {
					return false;
				}
			} finally {
				if (oRead != null) {
					oRead.Close();
				}
			}
		}
		private void PurgeCache(string folder)
		{
			System.Text.StringBuilder filesNotDeleted = new System.Text.StringBuilder();
			int i = 0;
			foreach (string File in Directory.GetFiles(folder, "*.resources")) {
				if (!DotNetNuke.Common.Utilities.FileSystemUtils.DeleteFileWithWait(File, 100, 200)) {
					filesNotDeleted.Append(String.Format("{0};", File));
				} else {
					i += 1;
				}
			}
			if (filesNotDeleted.Length > 0) {
				throw new IOException(String.Format("Deleted {0} files, however, some files are locked.  Could not delete the following files: {1}", i, filesNotDeleted));
			}
		}

        private static bool IsPathInApplication(string cacheFolder)
        {
            return cacheFolder.Contains(DotNetNuke.Common.Globals.ApplicationMapPath);
        }

	    public override string GenerateCacheKey(int tabModuleId, SortedDictionary<string, string> varyBy)
		{
			StringBuilder cacheKey = new StringBuilder();
			if (varyBy != null) {
				SortedDictionary<string, string>.Enumerator varyByParms = varyBy.GetEnumerator();
				while ((varyByParms.MoveNext())) {
					string key = varyByParms.Current.Key.ToLower();
					cacheKey.Append(string.Concat(key, "=", varyByParms.Current.Value, "|"));
				}
			}
			return GenerateCacheKeyHash(tabModuleId, cacheKey.ToString());
		}
		public override int GetItemCount(int tabModuleId)
		{
			return GetCachedItemCount(tabModuleId);
		}
		public override byte[] GetModule(int tabModuleId, string cacheKey)
		{
			string cachedModule = GetCachedOutputFileName(tabModuleId, cacheKey);
			BinaryReader br = null;
			FileStream fStream = null;
			byte[] data;
			try {
				if (!File.Exists(cachedModule)) {
					return null;
				}
				FileInfo fInfo = new FileInfo(cachedModule);
				long numBytes = fInfo.Length;
				fStream = new FileStream(cachedModule, FileMode.Open, FileAccess.Read);
				br = new BinaryReader(fStream);
				data = br.ReadBytes(Convert.ToInt32(numBytes));
			} finally {
				if (br != null)
					br.Close();
				if (fStream != null)
					fStream.Close();
			}
			return data;
		}
		public override void PurgeCache(int portalId)
		{
			PurgeCache(GetCacheFolder(portalId));
		}
		public override void PurgeExpiredItems(int portalId)
		{
			System.Text.StringBuilder filesNotDeleted = new System.Text.StringBuilder();
			int i = 0;
			string cacheFolder = GetCacheFolder(portalId);
            if (Directory.Exists(cacheFolder) && IsPathInApplication(cacheFolder))
			{
				foreach (string File in Directory.GetFiles(cacheFolder, String.Format("*{0}", FileProvider.AttribFileExtension)))
				{
					if (IsFileExpired(File))
					{
						string fileToDelete = File.Replace(FileProvider.AttribFileExtension, FileProvider.DataFileExtension);
						if (!DotNetNuke.Common.Utilities.FileSystemUtils.DeleteFileWithWait(fileToDelete, 100, 200))
						{
							filesNotDeleted.Append(String.Format("{0};", fileToDelete));
						}
						else
						{
							i += 1;
						}
					}
				}
			}
			if (filesNotDeleted.Length > 0)
			{
				throw new IOException(String.Format("Deleted {0} files, however, some files are locked.  Could not delete the following files: {1}", i, filesNotDeleted));
			}
		}
		public override void SetModule(int tabModuleId, string cacheKey, TimeSpan duration, byte[] output)
		{
			string attribFile = GetAttribFileName(tabModuleId, cacheKey);
			string cachedOutputFile = GetCachedOutputFileName(tabModuleId, cacheKey);
			try {
				if (File.Exists(cachedOutputFile)) {
					DotNetNuke.Common.Utilities.FileSystemUtils.DeleteFileWithWait(cachedOutputFile, 100, 200);
				}
				FileStream captureStream = new FileStream(cachedOutputFile, FileMode.CreateNew, FileAccess.Write);
				captureStream.Write(output, 0, output.Length);
				captureStream.Close();
				System.IO.StreamWriter oWrite;
				oWrite = File.CreateText(attribFile);
				oWrite.WriteLine(System.DateTime.UtcNow.Add(duration).ToString(CultureInfo.InvariantCulture));
				oWrite.Close();
			} catch (Exception ex) {
				DotNetNuke.Services.Exceptions.Exceptions.LogException(ex);
			}
		}
		public override void Remove(int tabModuleId)
		{
		    ModuleController controller = new ModuleController();
		    ModuleInfo tabModule = controller.GetTabModule(tabModuleId);
		    string cacheFolder = GetCacheFolder(tabModule.PortalID);
			System.Text.StringBuilder filesNotDeleted = new System.Text.StringBuilder();
			int i = 0;
            foreach (string File in Directory.GetFiles(cacheFolder, tabModuleId.ToString() + "_*.*"))
            {
				if (!DotNetNuke.Common.Utilities.FileSystemUtils.DeleteFileWithWait(File, 100, 200)) {
					filesNotDeleted.Append(File + ";");
				} else {
					i += 1;
				}
			}
			if (filesNotDeleted.Length > 0)
			{
				throw new IOException("Deleted " + i.ToString() + " files, however, some files are locked.  Could not delete the following files: " + filesNotDeleted.ToString());
			}
		}
	}
}
